﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace FGrubert.CrmQueueMonitor.WinForms.Properties
{
    /// <summary>
    /// Extension of the existing settings
    /// </summary>
    internal sealed partial class Settings
    {
        /// <summary>
        /// Stores the queue configs 
        /// </summary>
        public QueueBase[] QueueConfigs
        {
            get
            {
                if (QueueConfigDoc != null)
                {
                    return QueueConfig.Deserialize(QueueConfigDoc);
                }
                else
                {
                    return new QueueConfig[0];
                }
            }
            set
            {
                QueueConfigDoc = QueueConfig.SerializeToXmlNode(value);
            }
        }

        /// <summary>
        /// Stores the information windows configs 
        /// </summary>
        public InfoMonitor.WindowConfiguration[] InfoWindowConfigs
        {
            get
            {
                if (InfoWindowConfigDoc != null)
                {
                    return InfoMonitor.WindowConfiguration.Deserialize(InfoWindowConfigDoc);
                }
                else
                {
                    return new InfoMonitor.WindowConfiguration[0];
                }
            }
            set
            {
                InfoWindowConfigDoc = InfoMonitor.WindowConfiguration.SerializeToXmlNode(value);
            }
        }

        /// <summary>
        /// Get or sets the password unencrypted
        /// </summary>
        public string Password
        {
            get
            {
                return AesDecryption(PasswordString);
            }
            set
            {
                PasswordString = AesEncryption(value);
            }
        }

        static private byte[] key = { 70, 108, 111, 114, 105, 97, 110, 32, 71, 114, 117, 98, 101, 114,
                                            116, 32, 119, 97, 114, 32, 72, 73, 69, 82};
        static private byte[] iv16Bit = { 1, 75, 105, 101, 108, 4, 22, 60, 61, 62, 78, 79, 84, 86, 84, 25 };

        /// <summary>
        /// Encrypts a string with AES Standard
        /// </summary>
        /// <param name="dataToEncrypt">The original string to encrypt</param>
        /// <returns>A base 64 respresentation of the encrypted data</returns>
        private static string AesEncryption(string dataToEncrypt)
        {
            var bytes = Encoding.Default.GetBytes(dataToEncrypt);
            using (var aes = new AesCryptoServiceProvider())
            {
                using (var ms = new MemoryStream())
                using (var encryptor = aes.CreateEncryptor(key, iv16Bit))
                using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);
                    cs.FlushFinalBlock();
                    var cipher = ms.ToArray();
                    return Convert.ToBase64String(cipher);
                }
            }
        }

        /// <summary>
        /// Decrypts a string with AES Standard
        /// </summary>
        /// <param name="dataToDecrypt">A base 64 representation of the encrypted data</param>
        /// <returns>The orgininal string</returns>
        private static string AesDecryption(string dataToDecrypt)
        {
            var bytes = Convert.FromBase64String(dataToDecrypt);
            using (var aes = new AesCryptoServiceProvider())
            {
                using (var ms = new MemoryStream())
                using (var decryptor = aes.CreateDecryptor(key, iv16Bit))
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
                {
                    cs.Write(bytes, 0, bytes.Length);
                    cs.FlushFinalBlock();
                    var cipher = ms.ToArray();
                    return Encoding.UTF8.GetString(cipher);
                }
            }
        }

        private static string _permanentRoamingConfigFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "Florian_Grubert\\CrmQueueMonitor\\UserSettings.config");
        private static string _permanentLocalConfigFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create), "Florian_Grubert\\CrmQueueMonitor\\UserSettings.config");

        /// <summary>
        /// Saves the current settings to a permanent store
        /// </summary>
        public void SavePermanent()
        {
            if (!Directory.Exists(Path.GetDirectoryName(_permanentRoamingConfigFile)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(_permanentRoamingConfigFile));
            }
            if (!Directory.Exists(Path.GetDirectoryName(_permanentLocalConfigFile)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(_permanentLocalConfigFile));
            }

            XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
            xmlWriterSettings.Indent = true;
            xmlWriterSettings.WriteEndDocumentOnClose = true;

            using (XmlWriter roamingWriter = XmlWriter.Create(Settings._permanentRoamingConfigFile, xmlWriterSettings))
            {
                roamingWriter.WriteStartDocument();
                roamingWriter.WriteStartElement("CrmQueueMonitor");

                using (XmlWriter localWriter = XmlWriter.Create(Settings._permanentLocalConfigFile, xmlWriterSettings))
                {
                    localWriter.WriteStartDocument();
                    localWriter.WriteStartElement("CrmQueueMonitor");

                    Type t = this.GetType();
                    PropertyInfo[] properties = t.GetProperties();
                    foreach (PropertyInfo property in properties)
                    {
                        // If property is user scope and roaming, then get from permanent file
                        if (Attribute.IsDefined(property, typeof(global::System.Configuration.UserScopedSettingAttribute)))
                        {
                            object value = property.GetValue(this);

                            if (value != null)
                            {
                                if (Attribute.IsDefined(property, typeof(global::System.Configuration.SettingsManageabilityAttribute))
                                    && property.GetCustomAttribute<global::System.Configuration.SettingsManageabilityAttribute>().Manageability == System.Configuration.SettingsManageability.Roaming)
                                {
                                    // This is a roaming element, write in the roaming config
                                    roamingWriter.WriteStartElement(property.Name);

                                    XmlSerializer serializer = new XmlSerializer(value.GetType(), new XmlRootAttribute());
                                    serializer.Serialize(roamingWriter, value);

                                    roamingWriter.WriteEndElement();
                                }
                                else
                                {
                                    // This is a local element, write in the local config
                                    localWriter.WriteStartElement(property.Name);

                                    XmlSerializer serializer = new XmlSerializer(value.GetType(), new XmlRootAttribute());
                                    serializer.Serialize(localWriter, value);

                                    localWriter.WriteEndElement();
                                }
                            }
                        }
                    }

                    localWriter.WriteEndElement();
                }

                roamingWriter.WriteEndElement();
            }
        }

        private static bool _permanentDefaultLoaded;

        /// <summary>
        /// Loads the values from the permanent config and returns the current configuration
        /// </summary>
        public static Settings PermanentDefault
        {
            get
            {
                if (!_permanentDefaultLoaded)
                {
                    LoadPermanentConfig();
                }
                return Default;
            }
        }

        /// <summary>
        /// Loads the values from the permanent config and writes the to the default object
        /// </summary>
        public static void LoadPermanentConfig()
        {
            try
            {
                XmlDocument roamingDoc = new XmlDocument();
                roamingDoc.Load(Settings._permanentRoamingConfigFile);
                XmlNode roamingRootNode = roamingDoc.SelectSingleNode("CrmQueueMonitor");

                XmlDocument localDoc = new XmlDocument();
                localDoc.Load(Settings._permanentLocalConfigFile);
                XmlNode localRootNode = localDoc.SelectSingleNode("CrmQueueMonitor");

                Settings defaultSettings = Settings.Default;

                Type t = defaultSettings.GetType();
                PropertyInfo[] properties = t.GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    // If property is user scope and roaming, then get from permanent file
                    if (Attribute.IsDefined(property, typeof(global::System.Configuration.UserScopedSettingAttribute)))
                    {

                        XmlNode contentNode = null;

                        if (Attribute.IsDefined(property, typeof(global::System.Configuration.SettingsManageabilityAttribute))
                            && property.GetCustomAttribute<global::System.Configuration.SettingsManageabilityAttribute>().Manageability == System.Configuration.SettingsManageability.Roaming)
                        {
                            contentNode = roamingRootNode.SelectSingleNode(property.Name);
                        }
                        else
                        {
                            contentNode = localRootNode.SelectSingleNode(property.Name);
                        }

                        if (contentNode != null)
                        {
                            using (TextReader reader = new StringReader(contentNode.InnerXml))
                            {
                                XmlSerializer serializer = new XmlSerializer(property.PropertyType);
                                object value = serializer.Deserialize(reader);

                                property.SetValue(defaultSettings, value);
                            }
                        }
                    }
                }


            }
            catch (Exception)
            {
                // In case of emergency: Do nothing but use the default
            }

            _permanentDefaultLoaded = true;
        }
    }
}
